#!/usr/bin/python
# -*-coding:Utf-8 -*

import random
import sys
import thread
import time

import joueur as m_joueur


class Partie :
	"""Classe représentant une partie iSketch avec :
	- un verrou
	- le registre des comptes des utilisateurs
	- les couples (login, mot de passe)
	- la liste des joueurs
	- la liste des spectateurs
	- les mots du dictionnaire
	- le nombre max de joueurs et de rounds
	- le timeout en secondes après découverte du mot
	- les points attribués de base
	- le nombre d'abus avant expulsion
	- la durée d'un round en secondes
	- le round actuel
	- le mot à deviner de ce round
	- le gagnant de ce round
	- le nombre de bonnes réponses pour ce round
	- les abus signalés pour ce round
	- la couleur de la ligne
	- la taille de la ligne
	- l'historique de la partie"""
	
	def __init__(self) :
		self.verrou = thread.allocate_lock()
		self.registre = "registre.txt"
		self.comptes = {}
		self.joueurs = []
		self.spectateurs = []
		self.mots = []
		self.max = 4
		self.timeout = 30
		self.points_de_base = 10
		self.limite_abus = 3
		self.duree_round = 60
		self.round = 0
		self.mot_a_deviner = ""
		self.gagnant = ""
		self.bonnes_reponses = 0
		self.abus = 0
		self.couleur_ligne = ["0", "0", "0"]
		self.taille_ligne = 1
		self.historique = []
		
		try :
			fichier = open(self.registre, "r")
			contenu = fichier.read()
			fichier.close()
			for ligne in contenu.split("\n") :
				if ligne.strip() != "" and not ligne.startswith("#") :
					m = ligne.split(":", 1)
					if m[0].strip() != "(broadcast)" :
						self.comptes[str(m[0].strip())] = str(m[1].strip())
		except :
			print("Erreur lors de la lecture du registre (comptes des utilisateurs).")
	
	
	""" #################### GESTION DES LISTES DE JOUEURS ET DE SPECTATEURS #################### """
	def nouveau_joueur(self, p_joueur) :
		"""Lance le joueur sans l'intégrer à la liste."""
		thread.start_new_thread(p_joueur.routine, (self,))
	
	
	def integrer_joueur(self, p_joueur, p_pseudo) :
		"""Teste si le joueur peut intégrer ou non la partie et renvoie le résultat."""
		# Si la partie a déjà commencé ou qu'il n'y a plus de place libre
		if self.round > 0 or len(self.joueurs) == self.max :
			message_erreur = "La partie a déjà commencé et/ou est pleine."
			print(message_erreur)
			p_joueur.envoyer("LISTEN/(broadcast)/ERREUR : {}\n".format(message_erreur))
			return False
		# Si le pseudo est déjà pris par un autre joueur
		for j in self.joueurs :
			if j.pseudo == p_pseudo :
				message_erreur = "Le pseudo {} est déjà utilisé.".format(p_pseudo)
				print(message_erreur)
				p_joueur.envoyer("LISTEN/(broadcast)/ERREUR : {}\n".format(message_erreur))
				return False
		# "(broadcast)" est réservé pour le serveur
		if p_pseudo == "(broadcast)" :
			message_erreur = "Le pseudo (broadcast) est réservé pour le serveur."
			print(message_erreur)
			p_joueur.envoyer("LISTEN/(broadcast)/ERREUR : {}\n".format(message_erreur))
			return False
		# Si le pseudo a été réservé
		if p_pseudo in self.comptes :
			message_erreur = "Le pseudo {} est protégé par un mot de passe.".format(p_pseudo)
			print(message_erreur)
			p_joueur.envoyer("LISTEN/(broadcast)/ERREUR : {}\n".format(message_erreur))
			return False
		self.integrer_joueur_suite(p_joueur, p_pseudo)
		return True
	
	
	def integrer_joueur_suite(self, p_joueur, p_pseudo) :
		"""Intègre le joueur à la partie."""
		p_joueur.pseudo = p_pseudo
		self.joueurs.append(p_joueur)
		p_joueur.envoyer("WELCOME/{}/\n".format(p_joueur.pseudo))
		message = "CONNECTED/{}/\n".format(p_joueur.pseudo)
		self.envoyer_a_tous(message)
		self.envoyer_aux_spectateurs(message)
		print("{} ({}) a rejoint la partie.".format(p_joueur.pseudo, p_joueur.adresse))
		if len(self.joueurs) == self.max :
			self.commencer_tour()
	
	
	def login(self, p_joueur, p_pseudo, p_mot_de_passe) :
		"""Vérifie le couple (login, mot de passe) et intègre le joueur si il est correct."""
		if p_pseudo in self.comptes and self.comptes[p_pseudo] == p_mot_de_passe :
			self.integrer_joueur_suite(p_joueur, p_pseudo)
			return True
		message_erreur = "Le login et/ou le mot de passe est incorrect."
		print(message_erreur)
		p_joueur.envoyer("LISTEN/(broadcast)/ERREUR : {}\n".format(message_erreur))
		return False
	
	
	def register(self, p_joueur, p_pseudo, p_mot_de_passe) :
		"""Réserve un pseudo si il est disponible et intègre le joueur."""
		if p_pseudo in self.comptes :
			message_erreur = "Le pseudo {} est déjà protégé par un mot de passe.".format(p_pseudo)
			print(message_erreur)
			p_joueur.envoyer("LISTEN/(broadcast)/ERREUR : {}\n".format(message_erreur))
			return False
		try :
			fichier = open(self.registre, "a")
			fichier.write("\n{}:{}".format(p_pseudo, p_mot_de_passe))
			fichier.close()
			self.comptes[str(p_pseudo)] = str(p_mot_de_passe)
			self.integrer_joueur_suite(p_joueur, p_pseudo)
		except :
			message_erreur = "Problème lors de la lecture du registre (comptes des utilisateurs)."
			print(message_erreur)
			p_joueur.envoyer("LISTEN/(broadcast)/ERREUR : {}\n".format(message_erreur))
			return False
		message = "Le pseudo {} a été protégé par un mot de passe.".format(p_pseudo)
		print(message)
		p_joueur.envoyer("LISTEN/(broadcast)/{}\n".format(message))
		return True
	
	
	def retirer_joueur(self, p_joueur) :
		"""Retire un joueur de la partie."""
		if p_joueur.pseudo != "" :
			message = "EXITED/{}/\n".format(p_joueur.pseudo)
			self.envoyer_a_tous(message)
			self.envoyer_aux_spectateurs(message)
		if p_joueur in self.joueurs :
			self.joueurs.remove(p_joueur)
			print("{} ({}) a quitté la partie.".format(p_joueur.pseudo, p_joueur.adresse))
		if self.round > 0 and p_joueur.est_dessinateur :
			self.terminer_tour()
	
	
	def nouveau_spectateur(self, p_spectateur) :
		"""Ajoute un spectateur à la partie."""
		thread.start_new_thread(p_spectateur.routine, (self,))
		p_spectateur.envoyer("SPECTATOR/\n")
		for message in self.historique :
			p_spectateur.envoyer(message)
		self.spectateurs.append(p_spectateur)
	
	
	def retirer_spectateur(self, p_spectateur) :
		"""Retire un spectateur de la partie."""
		if p_spectateur in self.spectateurs :
			self.spectateurs.remove(p_spectateur)
	
	
	""" #################### ENVOIS DE MESSAGES #################### """
	def envoyer_a_tous(self, p_message) :
		"""Envoie un message à tous les joueurs."""
		for j in self.joueurs :
			try :
				j.envoyer(p_message)
			except :
				print("Erreur lors de l'envoi à {}.".format(j.pseudo))
	
	
	def envoyer_a_tous_sauf(self, p_message, p_joueur) :
		"""Envoie un message à tous les joueurs sauf celui spécifié."""
		for j in self.joueurs :
			if j.adresse != p_joueur.adresse :
				j.envoyer(p_message)
	
	
	def envoyer_aux_spectateurs(self, p_message) :
		"""Envoie un message à tous les spectateurs."""
		for s in self.spectateurs :
			s.envoyer(p_message)
	
	
	""" #################### GESTION DES MESSAGES ÉCHANGÉS #################### """
	def cheat(self, p_joueur, p_pseudo) :
		"""Signale un abus du dessinateur et l'expulse si il a été suffisament signalé."""
		if self.round > 0 :
			for j in self.joueurs :
				if j.est_dessinateur and j.pseudo == p_pseudo :
					self.abus += 1
					p_joueur.a_signale_un_abus = True
					message = "LISTEN/(broadcast)/{} a signalé un abus de {} (avertissement {}/{})./\n".format(p_joueur.pseudo, p_pseudo, self.abus, self.limite_abus)
					self.envoyer_a_tous(message)
					self.envoyer_aux_spectateurs(message)
					if self.abus == self.limite_abus :
						message = "EXITED/{}/\n".format(p_pseudo)
						self.envoyer_a_tous(message)
						self.envoyer_aux_spectateurs(message)
						self.historique.append(message)
						j.deconnecter()
						self.joueurs.remove(j)
						message = "LISTEN/(broadcast)/{} a été violemment expulsé pour avoir trop abusé./\n".format(p_pseudo)
						self.envoyer_a_tous(message)
						self.envoyer_aux_spectateurs(message)
						self.terminer_tour()
					break
	
	
	def courbe(self, p_joueur, p_x1, p_y1, p_x2, p_y2, p_x3, p_y3, p_x4, p_y4) :
		"""Courbe de Bézier."""
		if self.round > 0 :
			message = "COURBE/{}/{}/{}/{}/{}/{}/{}/{}/{}/{}/{}/{}/\n".format(p_x1, p_y1, p_x2, p_y2, p_x3, p_y3, p_x4, p_y4, self.couleur_ligne[0], self.couleur_ligne[1], self.couleur_ligne[2], self.taille_ligne)
			self.envoyer_a_tous_sauf(message, p_joueur)
			self.envoyer_aux_spectateurs(message)
			self.historique.append(message)
	
	
	def guess(self, p_joueur, p_mot) :
		# Si le mot proposé est le bon
		if self.round > 0 :
			if p_mot == self.mot_a_deviner :
				p_joueur.augmenter_score(self.points_de_base - min(self.bonnes_reponses, 5))
				self.bonnes_reponses += 1
				message = "WORD_FOUND/{}/\n".format(p_joueur.pseudo)
				self.envoyer_a_tous(message)
				self.envoyer_aux_spectateurs(message)
				self.historique.append(message)
				# Si le joueur est le premier à trouver
				if self.bonnes_reponses == 1 :
					# Le joueur a gagné le round
					self.gagnant = p_joueur.pseudo
					message = "WORD_FOUND_TIMEOUT/{}/\n".format(self.timeout)
					self.envoyer_a_tous_sauf(message, p_joueur)
					self.envoyer_aux_spectateurs(message)
					# Si il y a plusieurs chercheurs
					if len(self.joueurs) > 2 :
						# Déclenchement du timeout
						thread.start_new_thread(self.declencher_timeout, (self.timeout, self.round,))
				if (len(self.joueurs) - 1) == self.bonnes_reponses :
					self.terminer_tour()
			else :
				message = "GUESSED/{}/{}/\n".format(p_mot, p_joueur.pseudo)
				self.envoyer_a_tous(message)
				self.envoyer_aux_spectateurs(message)
				self.historique.append(message)
	
	
	def passed(self) :
		if self.round > 0 and self.bonnes_reponses == 0 :
			self.terminer_tour()
	
	
	def talk(self, p_joueur, p_message) :
		message = "LISTEN/{}/{}/\n".format(p_joueur.pseudo, p_message)
		self.envoyer_a_tous(message)
		self.envoyer_aux_spectateurs(message)
		self.historique.append(message)
	
	
	def set_color(self, p_r, p_g, p_b) :
		if self.round > 0 :
			# On s'assure que chaque couleur est comprise entre 0 et 255
			p_r = str(max(0, min(int(p_r), 255)))
			p_g = str(max(0, min(int(p_g), 255)))
			p_b = str(max(0, min(int(p_b), 255)))
			self.couleur_ligne = [p_r, p_g, p_b]
	
	
	def set_line(self, p_joueur, p_x1, p_y1, p_x2, p_y2) :
		if self.round > 0 :
			message = "LINE/{}/{}/{}/{}/{}/{}/{}/{}/\n".format(p_x1, p_y1, p_x2, p_y2, self.couleur_ligne[0], self.couleur_ligne[1], self.couleur_ligne[2], self.taille_ligne)
			self.envoyer_a_tous_sauf(message, p_joueur)
			self.envoyer_aux_spectateurs(message)
			self.historique.append(message)
	
	
	def set_size(self, p_s) :
		if self.round > 0 :
			self.taille_ligne = p_s
	
	
	""" #################### GESTION DES ROUNDS #################### """
	def commencer_tour(self) :
		self.round += 1
		if self.round == 1 :
			print("Début de la partie.")
		# Si un joueur (ou plusieurs) s'est déconnecté en cours de partie
		if self.round > len(self.joueurs) :
			self.terminer_partie()
			return False
		# Réinitialiser la variable du round
		self.gagnant = ""
		self.bonnes_reponses = 0
		self.abus = 0
		self.couleur_ligne = ["0", "0", "0"]
		self.taille_ligne = 1
		for j in self.joueurs :
			j.nouveau_tour()
		# Changement de dessinateur
		dessinateur = self.joueurs[self.round - 1]
		dessinateur.est_dessinateur = True
		# Changement du mot à deviner
		i = int(random.random() * len(self.mots))
		if self.mots[i] == self.mot_a_deviner :
			i = (i + 1) / len(self.mots)
		self.mot_a_deviner = self.mots[i]
		message = "NEW_ROUND/dessinateur/{}/{}/\n".format(dessinateur.pseudo, self.mot_a_deviner)
		dessinateur.envoyer(message)
		self.envoyer_aux_spectateurs(message)
		self.historique.append(message)
		self.envoyer_a_tous_sauf("NEW_ROUND/dessinateur/{}/\n".format(dessinateur.pseudo), dessinateur)
		# Durée limite du tour
		thread.start_new_thread(self.declencher_timeout, (self.duree_round, self.round,))
	
	
	def terminer_tour(self) :
		message = "END_ROUND/{}/{}/\n".format(self.gagnant, self.mot_a_deviner)
		self.envoyer_a_tous(message)
		self.envoyer_aux_spectateurs(message)
		self.historique.append(message)
		message = "SCORE_ROUND/"
		for j in self.joueurs :
			if j.est_dessinateur and self.bonnes_reponses > 0 :
				score = self.points_de_base + min((self.bonnes_reponses - 1), 5)
				j.augmenter_score(score)
			message += "{}/{}/".format(j.pseudo, j.score)
		message += "\n"
		self.envoyer_a_tous(message)
		self.envoyer_aux_spectateurs(message)
		self.historique.append(message)
		if self.round < self.max :
			self.commencer_tour()
		else :
			self.terminer_partie()
	
	
	def terminer_partie(self) :
		self.round = -1
		message = "LISTEN/(broadcast)/La partie est terminée./\n"
		self.envoyer_a_tous(message)
		self.envoyer_aux_spectateurs(message)
		print("Partie terminée.")
	
	
	def declencher_timeout(self, p_timeout, p_round) :
		time.sleep(p_timeout)
		self.verrou.acquire()
		if self.round == p_round :
			self.terminer_tour()
		self.verrou.release()
